//@author A0097846J
package tasky.ui;

import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.text.ParseException;
import java.util.logging.FileHandler;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.border.Border;

import net.miginfocom.swing.MigLayout;

import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
import org.junit.Assert;

import tasky.logic.Logic;
import tasky.logic.Result;

/**
 * This Class maintains Tasky's JFrame, which contains all of the UI's
 * components, and governs the communication between these components (As part
 * of Law of Demeter).
 * 
 * This Class also controls how the components are displayed to the user, as
 * well as pass information to the Logic component of Tasky
 * 
 */

public class BaseWindow {

	private static final int NUM_COLOR_INPUT = 3;
	private static final int BG_RED = 255;
	private static final int BG_BLUE = 255;
	private static final int BG_GREEN = 105;
	private static final int SCROLL_INCREMENT = 1;

	private static final String NAME_VERSION = "Tasky V0.4";
	private static final String NAME_SHUTDOWN = "Shutdown-thread";
	private static final String NAME_CLOSEBUTTON = "X";

	private static final String KEYWORD_SPACE = " ";
	
	private static final String MESSAGE_DEFAULT = "Welcome To Tasky! Number of tasks due today: ";

	private static final String ERROR_NATIVEHOOKFAIL = "Native Hook failed to register!";
	private static final String ERROR_RESULTFAIL = "Unable to process Command! Try again!";

	private static final String LOGGER_NATIVEHOOKFAIL = "NATIVE HOOK FAILS TO INITIALIZE.\n ";
	private static final String LOGGER_BORDERSET = "Borders Set";
	private static final String LOGGER_NAME = "BaseFrameLogger.log";
	private static final String LOGGER_INITIALIZE = "Initialized Base Frame Logger";

	private static final String MIG_WRAPVALUE = "wrap 1";
	private static final String MIG_ROWCONDITIONS = "";
	private static final String MIG_COLCONDITIONS = "";
	private static final String MIG_CELLGAP = "gapleft 10";

	private static final String MIG_MSGDIMENSIONS = "height 55:55:55, width 580:580:580";
	private static final String MIG_DISPLAYDIMENSIONS = "height 500:500:500, width 580:580:580";
	private static final String MIG_INPUTDIMENSIONS = "height :50:, width 580:580:580";
	private static final String MIG_HELPDIMENSIONS = "height :675:, width 450:450:450";
	private static final String MIG_TITLEPANEDIMENSIONS = "push, al right";

	private static final String MIG_TITLEDIMENSIONS = "gapright 340, height 50:50:50";
	private static final String MIG_BUTTONDIMENSIONS = "push, al right";

	private static final boolean FRAME_VISIBILITY = true;
	private static final boolean FRAME_RESIZABLE = false;
	private static boolean isBaseFrameIsCollapsed = true;

	private static final String COMMAND_DISPLAY = "display";
	private static final String COMMAND_ADD = "add";
	private static final String COMMAND_EDIT = "edit";
	private static final String COMMAND_REDO = "redo";
	private static final String COMMAND_DELETE = "delete";
	private static final String COMMAND_HISTORY = "history";
	private static final String COMMAND_DONE = "done";
	private static final String COMMAND_UNDONE = "undone";

	private static final String URL_TRAYICON = "/images/taskyTrayIcon.png";
	private static final String URL_TASKYLOGO = "/images/taskyIconTrans.png";

	private JFrame baseFrame;

	private TextWindowInterface topWindow;
	private TextWindowInterface botWindow;
	private TextWindowInterface msgWindow;
	private TextWindowInterface helpWindow;

	private JScrollPane scrollPaneTop;
	private JScrollPane scrollPaneMsg;
	private JScrollPane scrollPaneHelp;
	private JPanel migTitlePane;

	private JLabel titleField;

	private TaskyButton closeB;

	private JPanel migMainPanel;
	private JPanel migHelpPanel;

	private MigLayout uiMainMigLayout;
	private MigLayout uiHelpMigLayout;
	private MigLayout titleMigLayout;

	private GlobalKeyListener prog_GlobalListener;

	private ColorGenInterface cGen;

	private Logic prog_Logic;

	private Logger baseLogger;

	/**
	 * Initializer method
	 * 
	 * @param: Logic - Connects the UI to the Logic component
	 */
	public BaseWindow(Logic progLog) {

		loadLog();

		initializeObjects(progLog);

		createScrollPanes();
	}

	private void createScrollPanes() {
		scrollPaneTop = createScrollPane(topWindow);
		scrollPaneMsg = createScrollPane(msgWindow);
		scrollPaneHelp = createScrollPane(helpWindow);
	}

	private void initializeObjects(Logic progLog) {
		prog_Logic = progLog;

		baseFrame = new JFrame(NAME_VERSION);

		msgWindow = new DisplayWindow(this);
		topWindow = new DisplayWindow(this);
		helpWindow = new DisplayWindow(this);
		botWindow = new UserInputWindow(this);

		prog_GlobalListener = new GlobalKeyListener(this);

		cGen = new ColorGenerator();
	}

	private void loadLog() {
		try {
			baseLogger = Logger.getLogger(LOGGER_NAME);
			FileHandler fh = null;
			fh = new FileHandler(LOGGER_NAME);
			baseLogger.addHandler(fh);
			SimpleFormatter formatter = new SimpleFormatter();
			fh.setFormatter(formatter);

			baseLogger.info(LOGGER_INITIALIZE);

		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private JScrollPane createScrollPane(TextWindowInterface topWindow2) {
		return new JScrollPane(topWindow2.getTextWindow(),
				JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
				JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
	}

	/**
	 * This method prepares all the UI components, lay them out onto this
	 * Class's JFrame, and displays the JFrame
	 */
	public void setUp() {

		prepareLayoutAndDesign();

		prepareFramesAndPanels();

		prepareListenerAndCaret();

		prepareGlobalListeners();
	}

	/**
	 * This method prepares the graphical side of the UI, such as preparing
	 * the Tray Icon, setting Borders to the UI Panel components and setting
	 * up the layout grid for the components
	 */
	private void prepareLayoutAndDesign() {
		designTrayIcon();

		setBordersToPanes();

		setMigLayout();
	}

	private void prepareListenerAndCaret() {
		setMouseListeners();

		prepareCaretAndFocus();

		setStartingMessage();
	}

	private void prepareGlobalListeners() {
		runShutDownHook();

		runNativeHookListener();

		startTheGlobalKeyListener();
	}

	private void prepareFramesAndPanels() {
		prepareHelpPanel();

		prepareTitlePanel();

		prepareBaseFrame();
	}

	private void designTrayIcon() {
		Toolkit kit = Toolkit.getDefaultToolkit();
		Image img = kit.createImage(getClass().getResource(URL_TRAYICON));
		baseFrame.setIconImage(img);
	}

	private void prepareTitlePanel() {

		createProgTitle();

		createCloseButton();

		addTitleComponents();

		setTitleBGColor();
	}

	private void setTitleBGColor() {
		migTitlePane.setBackground(cGen
				.generateColor(BG_RED, BG_BLUE, BG_GREEN));
	}

	private void addTitleComponents() {
		migTitlePane.add(titleField, MIG_TITLEDIMENSIONS);

		migTitlePane.add(closeB, MIG_BUTTONDIMENSIONS);
	}

	private void createProgTitle() {

		BufferedImage img;
		
		try {
			img = ImageIO.read(getClass().getResource(URL_TASKYLOGO));
			
			img = resize(img, 175, 50);

			ImageIcon icon = new ImageIcon(img);

			titleField = new JLabel();

			titleField.setIcon(icon);
		} catch (IOException e) {
			// Do not run the program!
			e.printStackTrace();
			Assert.fail();
		}
	}

	/**
	 * This method resizes a BufferedImage and throws it back
	 * 
	 * @param img
	 * 			The BufferedImage to be resized
	 * @param newW
	 * 			The new Width
	 * @param newH
	 * 			The new Height
	 * @return
	 * 			The resized BufferedImage
	 */
	private static BufferedImage resize(BufferedImage img, int newW, int newH) {
		int w = img.getWidth();
		int h = img.getHeight();

		BufferedImage dimg = new BufferedImage(newW, newH, img.getType());
		Graphics2D g = dimg.createGraphics();

		rerenderImage(img, newW, newH, w, h, g);
		
		return dimg;
	}

	private static void rerenderImage(BufferedImage img, int newW, int newH,
			int w, int h, Graphics2D g) {
		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g.drawImage(img, 0, 0, newW, newH, 0, 0, w, h, null);
		g.dispose();
	}

	private void createCloseButton() {
		closeB = new TaskyButton(NAME_CLOSEBUTTON);
		closeB.setBorder(null);
		closeB.setBackground(Color.orange);
		closeB.setHoverBackgroundColor(Color.orange.brighter());
		closeB.setPressedBackgroundColor(Color.orange.darker());
		closeB.setFocusable(false);
		closeB.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				System.exit(1);
			}
		});
	}

	private void setStartingMessage() {
		updateNoOfTasksToday();

		displayAllTasksToday();
	}

	private void displayAllTasksToday() {
		((DisplayWindow) topWindow).addDisplayText(prog_Logic.getContentOfTasksToday());
	}

	private void updateNoOfTasksToday() {
		int noOfT = prog_Logic.getNumOfTasksToday();

		msgWindow.clearAllMsg();
		((DisplayWindow) msgWindow).addNewAnnoucement(MESSAGE_DEFAULT + noOfT);
	}

	/**
	 * Sets the Focus to the User input and prepare the Caret for Key Listening.
	 * Warning: Focus must be set BEFORE setting Caret listening
	 */
	private void prepareCaretAndFocus() {
		setFocusToUserInput();

		passCaretInfoForInput();
	}

	private void passCaretInfoForInput() {
		((UserInputWindow) botWindow).prepareCaretInfo();
	}

	private void startTheGlobalKeyListener() {
		GlobalScreen.getInstance().addNativeKeyListener(prog_GlobalListener);
	}

	private void runNativeHookListener() {
		try {
			GlobalScreen.registerNativeHook();
		} catch (NativeHookException ex) {
			System.err.println(ERROR_NATIVEHOOKFAIL);
			System.err.println(ex.getMessage());

			baseLogger.warning(LOGGER_NATIVEHOOKFAIL + ex.getMessage());

			System.exit(1);
		}
	}

	public void setFocusToUserInput() {
		((UserInputWindow) botWindow).setTheFocus();
	}

	private void runShutDownHook() {
		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
			public void run() {
				GlobalScreen.unregisterNativeHook();
			}
		}, NAME_SHUTDOWN));
	}

	/**
	 * Starts the Frame for the Program. Warning: Methods should be run in
	 * sequence!
	 */
	private void prepareBaseFrame() {

		// Mig Panel MUST be ready!
		Assert.assertNotNull(migMainPanel);

		frameSetVisualDetails();

		frameAddComponents();

		frameSetPosition();

		frameAppear();
	}

	private void frameAppear() {
		baseFrame.setVisible(FRAME_VISIBILITY);
	}

	private void frameSetPosition() {

		baseFrame.pack();

		baseFrame.setLocationRelativeTo(null);
	}

	private void frameSetVisualDetails() {
		baseFrame.setLayout(new MigLayout());

		baseFrame.setUndecorated(true);

		baseFrame.getContentPane().setBackground(Color.ORANGE);

		baseFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		baseFrame.setResizable(FRAME_RESIZABLE);
	}

	private void frameAddComponents() {
		baseFrame.add(migMainPanel);

	}

	private void setMouseListeners() {

		MoveMouseListener mover = new MoveMouseListener(baseFrame);

		addMouseListenerToBaseFrame(mover);

		addMouseListenerToMsgWindow(mover);

		addMouseListenerToUserInput(mover);

		addMouseListenerToHelpWindow(mover);
	}

	private void addMouseListenerToHelpWindow(MoveMouseListener mover) {
		helpWindow.getTextWindow().addMouseListener(mover);
		helpWindow.getTextWindow().addMouseMotionListener(mover);
	}

	private void addMouseListenerToUserInput(MoveMouseListener mover) {
		botWindow.getTextWindow().addMouseListener(mover);
		botWindow.getTextWindow().addMouseMotionListener(mover);
	}

	private void addMouseListenerToMsgWindow(MoveMouseListener mover) {
		msgWindow.getTextWindow().addMouseListener(mover);
		msgWindow.getTextWindow().addMouseMotionListener(mover);
	}

	private void addMouseListenerToBaseFrame(MoveMouseListener mover) {
		baseFrame.addMouseListener(mover);
		baseFrame.addMouseMotionListener(mover);
	}

	private JPanel setMigLayout() {
		createMainVisualDetails();
		createTitleVisualDetails();

		addMainComponents();

		return migMainPanel;

	}

	private void createTitleVisualDetails() {
		titleMigLayout = new MigLayout();
		migTitlePane = new JPanel(titleMigLayout);

	}

	private void createMainVisualDetails() {
		uiMainMigLayout = new MigLayout(MIG_WRAPVALUE, MIG_COLCONDITIONS,
				MIG_ROWCONDITIONS);

		migMainPanel = new JPanel(uiMainMigLayout);

		migMainPanel.setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2,
				Color.BLACK));

		migMainPanel.setBackground(Color.ORANGE);
	}

	private void addMainComponents() {

		migMainPanel.add(migTitlePane, MIG_TITLEPANEDIMENSIONS);
		migMainPanel.add(scrollPaneMsg, MIG_MSGDIMENSIONS);
		migMainPanel.add(scrollPaneTop, MIG_DISPLAYDIMENSIONS);
		migMainPanel.add(botWindow.getTextWindow(), MIG_INPUTDIMENSIONS);
	}

	private void setBordersToPanes() {
		Border NONTYPING = BorderFactory.createMatteBorder(2, 2, 2, 2,
				Color.BLACK);
		Border TYPING = BorderFactory
				.createMatteBorder(2, 2, 2, 2, Color.BLACK);
		Border MAIN = BorderFactory.createMatteBorder(2, 2, 2, 2, Color.WHITE);

		scrollPaneMsg.setBorder(NONTYPING);
		scrollPaneTop.setBorder(NONTYPING);
		botWindow.getTextWindow().setBorder(TYPING);

		((JComponent) baseFrame.getContentPane()).setBorder(MAIN);

		baseLogger.info(LOGGER_BORDERSET);
	}

	public JFrame getParentFrame() {
		return baseFrame;
	}

	public void moveDisplayUp() {
		scrollPaneTop.getVerticalScrollBar().setValue(
				scrollPaneTop.getVerticalScrollBar().getValue()
						- scrollPaneTop.getVerticalScrollBar()
								.getUnitIncrement(SCROLL_INCREMENT));
	}

	public void moveDisplayDown() {
		scrollPaneTop.getVerticalScrollBar().setValue(
				scrollPaneTop.getVerticalScrollBar().getValue()
						+ scrollPaneTop.getVerticalScrollBar()
								.getUnitIncrement(SCROLL_INCREMENT));
	}

	private void prepareHelpPanel() {
		setHelpVisualDetails();

		addHelpComponents();

		extractHelpInfo();
	}

	private void extractHelpInfo() {
		try {
			((DisplayWindow) helpWindow).addDefaultText(prog_Logic.getHelp());
		} catch (Exception e) {
			passError(e.getMessage());
		}
	}

	private void addHelpComponents() {
		migHelpPanel.add(scrollPaneHelp, MIG_HELPDIMENSIONS);
	}

	private void setHelpVisualDetails() {
		uiHelpMigLayout = new MigLayout(MIG_WRAPVALUE, MIG_COLCONDITIONS,
				MIG_ROWCONDITIONS);

		migHelpPanel = new JPanel(uiHelpMigLayout);

		migHelpPanel.setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2,
				Color.BLACK));

		migHelpPanel.setBackground(Color.ORANGE.darker());
	}

	public void changeColor(String userInput) throws Exception {
		String[] colorInt = userInput.split(KEYWORD_SPACE);
		Color result;

		if (isCorrNumOfColorInput(colorInt)) {
			result = generateColorByInt(colorInt);
		} else
			result = generateColorByString(userInput);

		setColorsToComponents(result);
	}

	private void setColorsToComponents(Color result) {
		migMainPanel.setBackground(result);
		migTitlePane.setBackground(result.brighter());
		baseFrame.getContentPane().setBackground(result);
		closeB.setBackground(result);
		closeB.setHoverBackgroundColor(result.brighter());
		closeB.setPressedBackgroundColor(result.darker());
	}

	private Color generateColorByString(String userInput) throws Exception {
		return cGen.generateColor(userInput);
	}

	private Color generateColorByInt(String[] colorInt) {
		return cGen.generateColor(Integer.parseInt(colorInt[0]),
				Integer.parseInt(colorInt[1]),
				Integer.parseInt(colorInt[2]));
	}

	private boolean isCorrNumOfColorInput(String[] colorInt) {
		return colorInt.length == NUM_COLOR_INPUT;
	}

	public void clearDisplay() {
		topWindow.clearAllMsg();
	}

	public void passResult(String enteredKey) {
		try {
			Result result = prog_Logic.processUserInput(enteredKey);

			updateNoOfTasksToday();

			if (result.isError()) {
				((DisplayWindow) topWindow).addErrorMsg(result.getOutput());
			} else {
				printSpecificCommand(result);

				if (result.getWarning()) {
					((DisplayWindow) topWindow).addWarningText(result.getWarningMessage());
				}
			}
			
			
		} catch (ParseException | IOException | ClassNotFoundException e) {
			((DisplayWindow) topWindow).addErrorMsg(ERROR_RESULTFAIL);
		}
	}

	private void printSpecificCommand(Result result) {
		
		switch (result.getTypeOfCommand()) {
		case COMMAND_DISPLAY:
			((DisplayWindow) topWindow).addDisplayText(result.getOutput());
			break;
		case COMMAND_ADD:
			((DisplayWindow) topWindow).addAddText(result.getOutput());
			break;
		case COMMAND_EDIT:
			((DisplayWindow) topWindow).addEditText(result.getOutput());
			break;
		case COMMAND_DELETE:
			((DisplayWindow) topWindow).addDeleteText(result.getOutput());
			break;
		case COMMAND_REDO:
			((DisplayWindow) topWindow).addUndoText(result.getOutput());
			break;
		case COMMAND_HISTORY:
			((DisplayWindow) topWindow).addHistoryText(result.getOutput());
			break;
		case COMMAND_DONE:
			((DisplayWindow) topWindow).addDoneText(result.getOutput());
			break;
		case COMMAND_UNDONE:
			((DisplayWindow) topWindow).addDoneText(result.getOutput());
			break;
		default:
			((DisplayWindow) topWindow).addNewAnnoucement(result.getOutput());
			break;
		}
	}

	public void passError(String errorMsg) {
		((DisplayWindow) topWindow).addErrorMsg(errorMsg);
	}

	public void popoutHelp() {
		if (isBaseFrameIsCollapsed) {
			openUpHelpWindow();
		} else {
			closeHelpWindow();
		}

	}

	private void closeHelpWindow() {
		baseFrame.remove(migHelpPanel);
		baseFrame.pack();
		isBaseFrameIsCollapsed = true;
	}

	private void openUpHelpWindow() {
		baseFrame.add(migHelpPanel, MIG_CELLGAP);
		baseFrame.pack();
		isBaseFrameIsCollapsed = false;
	}

	public void miniProgram() {
		baseFrame.setState(Frame.ICONIFIED);
	}

	public void maxiProgram() {
		baseFrame.setState(Frame.NORMAL);
	}

	public int getStateOfProgram() {
		return baseFrame.getState();
	}
	
	public void scrollToBottom() { 
		JScrollBar vertical = scrollPaneTop.getVerticalScrollBar();
		vertical.setValue( vertical.getMaximum() );
	}

}